# [十四] Spring的事务管理 - 隔离级别
导读
MySQL官方对隔离界别的定义:
The transaction isolation level of a SQL-transaction defines the degree to which the operations on SQL-data, or schemas in that SQL-transaction are affected by the effects of and can affect operations on SQL-data or schemas in concurrent SQL-transactions
.
大概意思就是说:"一个sql事务的事务隔离级别定义的sql数据上的操作,或模式的sql事务受到的影响,会影响操作的sql数据并发sql事务或模式"。简单地说,隔离级别定义了并发事务在修改数据时的交互方式(如何相互影响的)。
Spring 事务中的隔离级别最终实现还是依赖数据库的,是在数据库的基础上进行了业务的包装,以便更好的和数据库结合,本身针对MySQL
数据库中的隔离级别进行举例说明。
# 隔离级别的分类
名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交 | ISOLATION_READ_UNCOMMITTED | 会 | 会 | 会 |
读提交 | ISOLATION_READ_COMMITTED | 不会 | 会 | 会 |
可重复读 | ISOLATION_REPEATABLE_READ | 不会 | 不会 | 会 |
串行化 | ISOLATION_SERIALIZABLE | 不会 | 不会 | 不会 |
首先可以查看MySQL数据库的默认隔离级别是
可重复读(RR)
select @@tx_isolation;
+-----------------------+
| @@tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
# REPEATABLE-READ(可重复读)
RR
可重复读(REPEATABLE-READ):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录(读已经提交的,其实是读早于本事务开始且已经提交的),但是不能看到其他事务对已有记录的更新(即晚于本事务开始的),并且该事务不要求与其他事务是“可串行化”的。
换句话说,就是在可重复读(Repeatable Read)的隔离级别下,其他事务线程添加了id=10
的数据并提交,那么当前线程是读取不到的,但是数据其实已经添加到了表中,也就是说如果在当前线程中再执行添加id=10
的数据会报错,提示当前id已经存在。
# 示例
- 首先,线程A执行到
select * from cp_goods
查询操作,并没有提交
# set transaction isolation level repeatable read;
begin ;
select * from cp_goods;
# commit ;
查询结果是:
1 test 100
2 test1 100
3 test2 100
- 此时,另外一个线程B由于业务需求要对表
cp_goods
进行修改,添加一条数据并提交
# set transaction isolation level repeatable read;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test3',100);
commit ;
- 然后,回到A再执行
select * from cp_goods
,发现查询结果并没有改变,这就是可重复读。
1 test 100
2 test1 100
3 test2 100
- 然后,A接着执行
commit
提交操作,再次执行select * from cp_goods
,发现可以查到数据了。
1 test 100
2 test1 100
3 test2 100
4 test3 100
在RR隔离级别下为事务设置了一个
一致性读视图(即快照)
,之后读取数据,就是根据这个快照来获取,这样,就不能看到他晚于本事务的事务对已有记录的更新,也就是为什么线程B提交后,A查询结果仍然可以保持不变。
由此可见,可重复读的隔离级别下使用了
MVCC机制
,使用的是Next-Key Lock
锁算法,select
操作不会更新版本号,是快照读(历史版本);insert
、update
和delete
会更新版本号,是当前读(当前版本)。
知识点
MVCC(多版本并发控制),InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。在读取事务开始时,系统会给事务一个当前版本号,事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。
# READ-UNCOMMITTED(读未提交)
提示
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
# 示例
- 首先,线程A执行到
select * from cp_goods
查询
set transaction isolation level read uncommitted ;
begin ;
select * from cp_goods;
# commit ;
查询结果是:
1 test 100
2 test1 100
3 test2 100
4 test3 100
- 此时,另外一个线程B由于业务需求要对表
cp_goods
进行修改,添加并修改一条数据,没有提交
set transaction isolation level read uncommitted ;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test4',100);
update cp_goods set count= count - 10 where id = 1;
# commit ;
- 然后,回到A再执行
select * from cp_goods
,发现查询读取到了B未提交的数据,这就是读未提交。
查询结果是:
1 test 90
2 test1 100
3 test2 100
4 test3 100
5 test4 100
# READ-COMMITTED(读提交)
提示
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
# 示例
- 首先,线程A执行到
select * from cp_goods
查询
set transaction isolation level read committed ;
begin ;
select * from cp_goods;
# commit ;
查询结果是:
1 test 100
2 test1 100
3 test2 100
4 test3 100
- 此时,另外一个线程B由于业务需求要对表
cp_goods
进行修改,添加并修改一条数据,并且提交
set transaction isolation level read committed ;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test5',100);
update cp_goods set count= count - 10 where id = 2;
commit ;
- 然后,回到A再执行
select * from cp_goods
,发现查询读取到了B提交的数据,这就是读提交。
查询结果是:
1 test 90
2 test1 90
3 test2 100
4 test3 100
5 test4 100
6 test5 100
知识点
MySQL常用的两种引擎MyISAM
和InnoDB
,MyISAM
默认使用表锁,InnoDB
默认使用行锁。
使用InnoDB
引擎,如果筛选条件里面没有索引字段,就会锁住整张表,否则的话,锁住相应的行。